1 /*
2  * Collie - An asynchronous event-driven network framework using Dlang development
3  *
4  * Copyright (C) 2015-2017  Shanghai Putao Technology Co., Ltd 
5  *
6  * Developer: putao's Dlang team
7  *
8  * Licensed under the Apache-2.0 License.
9  *
10  */
11 module collie.codec.http.server.responsebuilder;
12 import kiss.logger;
13 import collie.codec.http.server.responsehandler;
14 import collie.codec.http.httpmessage;
15 import collie.codec.http.codec.httpcodec;
16 import collie.codec.http.headers;
17 import kiss.container.ByteBuffer;
18 import std.experimental.allocator.mallocator;
19 
20 import std.conv;
21 
22 /**
23 */
24 class ResponseBuilder
25 {
26 	this()
27 	{
28 		_body = new ByteBuffer!Mallocator();
29 		_httpMessage = new HttpMessage(HttpMessageType.response);
30 	}
31 
32 	this(ResponseHandler txn)
33 	{
34 		_txn = txn;
35 		this();
36 	}
37 
38 	final ResponseBuilder promise(string url, string host)
39 	{
40 		_httpMessage.url(url);
41 		_httpMessage.setHeader(HTTPHeaderCode.HOST, host);
42 		return this;
43 	}
44 
45 	final ResponseBuilder status(ushort code, string message)
46 	{
47 		debug logDebug("status: ", code, "  message: ", message);
48 
49 		_httpMessage.statusCode(code);
50 		_httpMessage.statusMessage(message);
51 
52 		return this;
53 	}
54 
55 	/**
56      * Get the status code for the response.
57      *
58      * @return int
59      */
60 	int status()
61 	{
62 		return _httpMessage.statusCode();
63 	}
64 
65 	/**
66      * Set a header on the Response.
67      *
68      * @param  string  $key
69      * @param  array|string  $values
70      * @return $this
71      */
72 	final ResponseBuilder header(T = string)(string name, T value)
73 	{
74 		_httpMessage.setHeader(name, to!string(value));
75 		return this;
76 	}
77 
78 	final ResponseBuilder header(T = string)(HttpHeaderCode code, T value)
79 	{
80 		_httpMessage.setHeader(code, to!string(value));
81 		return this;
82 	}
83 
84 	/**
85      * Add an array of headers to the response.
86      *
87      * @param  array  $headers
88      * @return $this
89      */
90 	ResponseBuilder withHeaders(string[string] headers)
91 	{
92 		validate();
93 		foreach (string key, string value; headers)
94 			_httpMessage.addHeader(key, value);
95 		return this;
96 	}
97 
98 	final ResponseBuilder setBody(in ubyte[] data)
99 	{
100 		_body.write(data);
101 		return this;
102 	}
103 
104 	final ResponseBuilder connectionClose()
105 	{
106 		return header(HTTPHeaderCode.CONNECTION, "close");
107 	}
108 
109 	final void sendWithEOM()
110 	{
111 		_sendEOM = true;
112 		send();
113 	}
114 
115 	final void send()
116 	{
117 		validate();
118 		
119 		bool chunked = true;
120 		if (_sendEOM)
121 			chunked = false;
122 	
123 		version(CollieDebugMode) 
124 		{
125 			// logDebug("is isResponse : ",_httpMessage.isResponse());
126 			logDebug("resonse status code: ", _httpMessage.statusCode);
127 		}
128 
129 		if (_httpMessage.statusCode >= 200 && _httpMessage.isResponse())
130 		{
131 			version (CollieDebugMode)
132 				logDebug("Chunked: ", chunked);
133 			if (chunked)
134 			{
135 				_httpMessage.chunked(true);
136 			}
137 			else
138 			{
139 				_httpMessage.chunked(false);
140 				_httpMessage.setHeader(HTTPHeaderCode.CONTENT_LENGTH, to!string(_body.length));
141 			}
142 		}
143 		if (_txn)
144 		{
145 			if ((_body.length == 0) && _sendEOM)
146 			{
147 				_txn.sendHeadersWithEOM(_httpMessage);
148 				return;
149 			}
150 			else
151 			{
152 				_txn.sendHeaders(_httpMessage);
153 			}
154 		}
155 		
156 		if ((_body.length > 0) && _txn)
157 		{
158 			version (CollieDebugMode)
159 				logDebug("body len = ", _body.length);
160 			if (chunked)
161 			{
162 				_txn.sendChunkHeader(_body.length);
163 				_txn.sendBody(_body.allData.data());
164 			}
165 			else
166 			{
167 				_txn.sendBody(_body.allData.data(), _sendEOM);
168 				return;
169 			}
170 			_body.clear();
171 		}
172 		if (_sendEOM && _txn)
173 		{
174 			_txn.sendEOM();
175 		}
176 	}
177 
178 	void clear()
179 	{
180 		_isDisposed = true;
181 		_txn = null;
182 	}
183 
184 	private void validate()
185 	{
186 		assert(!_isDisposed, "The resources have been released!");
187 	}
188 
189 	@property HttpMessage httpMessage()
190 	{
191 		return _httpMessage;
192 	}
193 
194 	final @property HttpMessage headers()
195 	{
196 		return _httpMessage;
197 	}
198 
199 	final @property ByteBuffer!Mallocator* bodys()
200 	{
201 		return &_body;
202 	}
203 
204 	@property ResponseHandler dataHandler()
205 	{
206 		return _txn;
207 	}
208 
209 	@property void dataHandler(ResponseHandler txn)
210 	{
211 		_txn = txn;
212 	}
213 
214 protected:
215 	pragma(inline) final void setResponseHandler(ResponseHandler txn){_txn = txn;}
216 
217 protected:
218 	HttpMessage _httpMessage;
219 	ByteBuffer!Mallocator _body;
220 	ResponseHandler _txn;
221 
222 private:
223 	bool _sendEOM = false;
224 	bool _isDisposed = false;
225 }